package jkoa.middleware;

import cn.hutool.core.io.FileUtil;
import cn.hutool.setting.Setting;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import jkoa.core.App;
import jkoa.core.Context;
import jkoa.core.Log;
import jkoa.core.Middleware;
import jkoa.core.Next;
import jkoa.core.Request;
import jkoa.core.Response;

public class NettyMiddleware  implements Middleware {
	private int port;

	public NettyMiddleware() {
		String nettyConfigPath = "netty.properties";
		if (!FileUtil.exist(nettyConfigPath)) {
			Log.error("file not exist,{}", nettyConfigPath);
		}

		Setting setting = new Setting(nettyConfigPath);
		port = setting.getInt("netty.port");

		this.listen(port);
	}

	public void listen(int port) {
//        NettyMiddleware nettyMiddleware = new NettyMiddleware();
		App.me().middlewares.add(this);

		Request request = new Request();
		Response response = new Response();
		Context ctx = new Context(request, response);

		this.executeMiddlewares(ctx);
	}
	
	

	public void executeMiddlewares(Context ctx) {
		Middleware composedMiddleware = composeMiddlewares();
		composedMiddleware.handle(ctx, new Next() {
			@Override
			public void execute(Context ctx) {
				// No-op for the end of the chain
			}
		});
	}

	public Middleware composeMiddlewares() {
		Middleware[] middlewareArray = App.me().middlewares.toArray(new Middleware[0]);
		return (ctx, next) -> {
			Middleware current = middlewareArray[middlewareArray.length - 1];
			for (int i = middlewareArray.length - 2; i >= 0; i--) {
				current = handleMiddleware(ctx, middlewareArray[i], current);
			}
//			return current.handle(ctx, next);
//			 current.handle(ctx, next);
		};
	}

	public Middleware handleMiddleware(Context ctx, Middleware middleware, Middleware next) {
//		return () -> middleware.handle(ctx, new Next() {
//			@Override
//			public void execute(Context ctx) {
//				next.handle(ctx, this);
//			}
//		});
		
		
		 middleware.handle(ctx, new Next() {
			@Override
			public void execute(Context ctx) {
				next.handle(ctx, this);
			}
		});
		
		return next;
		
	}
	
	
	EventLoopGroup bossGroup;

	@Override
	public void handle(Context ctx, Next next) {
		
		if(bossGroup!=null) {
			
			next.execute(ctx);
			
			
			return;
		}
		
		 bossGroup = new NioEventLoopGroup();
		EventLoopGroup workerGroup = new NioEventLoopGroup();
		try {
			ServerBootstrap bootstrap = new ServerBootstrap();
			bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
					.childHandler(new ChannelInitializer<SocketChannel>() {
						@Override
						protected void initChannel(SocketChannel ch) throws Exception {
							ch.pipeline().addLast(new HttpRequestDecoder());
							ch.pipeline().addLast(new HttpObjectAggregator(65536));
							ch.pipeline().addLast(new HttpResponseEncoder());
							ch.pipeline().addLast(new ChunkedWriteHandler());
							ch.pipeline().addLast(new WebSocketServerProtocolHandler("/ws"));
							ch.pipeline().addLast(new NettyRequestHandler(next));
						}
					}).option(ChannelOption.SO_BACKLOG, 128).childOption(ChannelOption.SO_KEEPALIVE, true);

			ChannelFuture f = bootstrap.bind(port).sync();

			System.out.println("Netty server started on port " + port);

			f.channel().closeFuture().sync();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			workerGroup.shutdownGracefully();
			bossGroup.shutdownGracefully();
		}
	}

	private static class NettyRequestHandler extends io.netty.channel.SimpleChannelInboundHandler<Object> {
		private final Next next;

		public NettyRequestHandler(Next next) {
			this.next = next;
		}

		@Override
		protected void channelRead0(io.netty.channel.ChannelHandlerContext ctx, Object msg) throws Exception {
			if (msg instanceof io.netty.handler.codec.http.HttpRequest) {
				io.netty.handler.codec.http.HttpRequest request = (io.netty.handler.codec.http.HttpRequest) msg;
				io.netty.handler.codec.http.HttpResponse response = new io.netty.handler.codec.http.DefaultHttpResponse(
						request.protocolVersion(), io.netty.handler.codec.http.HttpResponseStatus.OK);
				Request r = new Request();
				r.setMethod(request.method().name());
				r.setPath(request.uri());
				Response res = new Response();
				Context context = new Context(r, res);
				next.execute(context);
				ctx.writeAndFlush(response);
			}
		}
	}
}